package com.liaoyifan.core.generator;

import com.liaoyifan.core.converter.handler.DateTimeHandler;
import com.liaoyifan.core.model.CoreController;
import com.liaoyifan.core.model.CoreConverter;
import com.liaoyifan.core.model.CoreDTO;
import com.liaoyifan.core.model.CoreEntity;
import com.liaoyifan.core.model.CoreMapper;
import com.liaoyifan.core.model.CoreService;
import com.liaoyifan.core.model.CoreServiceImpl;
import com.liaoyifan.core.model.CoreVO;
import com.liaoyifan.core.util.CaseFormat;
import com.liaoyifan.core.util.Try;
import com.liaoyifan.core.valid.Assert;
import com.liaoyifan.core.valid.Lang;
import com.zaxxer.hikari.HikariDataSource;
import java.nio.file.OpenOption;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.AccessLevel;
import lombok.Generated;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.core.env.PropertySource;

@SuppressWarnings({"unused", "FieldCanBeLocal", "MismatchedQueryAndUpdateOfCollection"})
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Generator {

    @Getter private final Map<String, Object> dataModel = new HashMap<>();

    private OpenOption[] options;

    public Generator options(OpenOption... options) {
        this.options = options;
        return this;
    }

    public static Generator.Builder builder() {
        return new Builder();
    }

    @SuppressWarnings("UnusedReturnValue")
    public static class Builder {
        private String yml;
        private String jdbcUrl;
        private String username;
        private String password;
        private String table;
        private String basePackage;
        private String author;
        private Class<?> baseEntity;
        private Class<?> baseDTO;
        private Class<?> baseVO;
        private Class<?> baseMapper;
        private Class<?> baseService;
        private Class<?> baseServiceImpl;
        private Class<?> baseController;
        private Class<?> baseConverter;

        public Builder baseMapper(final Class<?> baseMapper) {
            this.baseMapper = baseMapper;
            return this;
        }

        public Builder baseService(final Class<?> baseService) {
            this.baseService = baseService;
            return this;
        }

        public Builder baseServiceImpl(final Class<?> baseServiceImpl) {
            this.baseServiceImpl = baseServiceImpl;
            return this;
        }

        public Builder baseController(final Class<?> baseController) {
            this.baseController = baseController;
            return this;
        }

        public Builder baseConverter(final Class<?> baseConverter) {
            this.baseConverter = baseConverter;
            return this;
        }

        public Builder baseEntity(final Class<?> baseEntity) {
            this.baseEntity = baseEntity;
            return this;
        }

        public Builder baseDTO(final Class<?> baseDTO) {
            this.baseDTO = baseDTO;
            return this;
        }

        public Builder baseVO(final Class<?> baseVO) {
            this.baseVO = baseVO;
            return this;
        }

        public Builder author(final String author) {
            this.author = author;
            return this;
        }

        public Builder yml(final String yml) {
            this.yml = yml;
            return this;
        }

        public Builder jdbcUrl(final String jdbcUrl) {
            this.jdbcUrl = jdbcUrl;
            return this;
        }

        public Builder username(final String username) {
            this.username = username;
            return this;
        }

        public Builder password(final String password) {
            this.password = password;
            return this;
        }

        public Builder basePackage(final String basePackage) {
            this.basePackage = basePackage;
            return this;
        }

        public Builder table(final String table) {
            this.table = table;
            return this;
        }

        @Generated
        public Generator build() {
            System.out.println("build Generator start");
            Assert.notBlank(this.author, "author cannot be blank");
            Assert.notBlank(this.table, "table cannot be blank");
            if (Lang.isBlank(this.yml)) {
                this.yml = "application-dev.yml";
            }
            if (Lang.isBlank(this.basePackage)) {
                this.basePackage = "com.liaoyifan";
            }
            PropertySource<?> property = GenerateUtil.getPropertySource(this.yml);
            this.jdbcUrl(
                    Lang.isBlank(this.jdbcUrl)
                            ? GenerateUtil.getDataSourceProperty(property, "jdbc-url")
                            : this.jdbcUrl);
            this.username(
                    Lang.isBlank(this.username)
                            ? GenerateUtil.getDataSourceProperty(property, "username")
                            : this.username);
            this.password(
                    Lang.isBlank(this.password)
                            ? GenerateUtil.getDataSourceProperty(property, "password")
                            : this.password);
            Generator generator = new Generator();
            Map<String, Object> model = generator.dataModel;
            model.put("author", this.author);
            model.put("since", DateTimeHandler.fromLocalDateTime(LocalDateTime.now()));
            model.put("table", this.table);
            model.put("basePackage", this.basePackage);
            String after = this.table.substring(this.table.indexOf("_") + 1).toLowerCase();
            model.put("varName", CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, after));
            model.put("moduleName", this.table.substring(0, this.table.indexOf("_")).toLowerCase());
            model.put("modulePackage", this.basePackage + "." + model.get("moduleName"));
            model.put("entityName", CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, after));
            model.put(
                    "entityPackage",
                    model.get("modulePackage") + "." + Model.ENTITY.name().toLowerCase());
            if (this.baseMapper != null) {
                model.put("baseMapperName", this.baseMapper.getName());
                model.put("baseMapperSimpleName", this.baseMapper.getSimpleName());
            } else {
                model.put("baseMapperName", CoreMapper.class.getName());
                model.put("baseMapperSimpleName", CoreMapper.class.getSimpleName());
            }
            if (this.baseService != null) {
                model.put("baseServiceName", this.baseService.getName());
                model.put("baseServiceSimpleName", this.baseService.getSimpleName());
            } else {
                model.put("baseServiceName", CoreService.class.getName());
                model.put("baseServiceSimpleName", CoreService.class.getSimpleName());
            }
            if (this.baseServiceImpl != null) {
                model.put("baseServiceImplName", this.baseServiceImpl.getName());
                model.put("baseServiceImplSimpleName", this.baseServiceImpl.getSimpleName());
            } else {
                model.put("baseServiceImplName", CoreServiceImpl.class.getName());
                model.put("baseServiceImplSimpleName", CoreServiceImpl.class.getSimpleName());
            }
            if (this.baseController != null) {
                model.put("baseControllerName", this.baseController.getName());
                model.put("baseControllerSimpleName", this.baseController.getSimpleName());
            } else {
                model.put("baseControllerName", CoreController.class.getName());
                model.put("baseControllerSimpleName", CoreController.class.getSimpleName());
            }
            if (this.baseConverter != null) {
                model.put("baseConverterName", this.baseConverter.getName());
                model.put("baseConverterSimpleName", this.baseConverter.getSimpleName());
            } else {
                model.put("baseConverterName", CoreConverter.class.getName());
                model.put("baseConverterSimpleName", CoreConverter.class.getSimpleName());
            }
            if (this.baseEntity != null) {
                model.put("baseEntityName", this.baseEntity.getName());
                model.put("baseEntitySimpleName", this.baseEntity.getSimpleName());
            } else {
                model.put("baseEntityName", CoreEntity.class.getName());
                model.put("baseEntitySimpleName", CoreEntity.class.getSimpleName());
            }
            if (this.baseDTO != null) {
                model.put("baseDTOName", this.baseDTO.getName());
                model.put("baseDTOSimpleName", this.baseDTO.getSimpleName());
            } else {
                model.put("baseDTOName", CoreDTO.class.getName());
                model.put("baseDTOSimpleName", CoreDTO.class.getSimpleName());
            }
            if (this.baseVO != null) {
                model.put("baseVOName", this.baseVO.getName());
                model.put("baseVOSimpleName", this.baseVO.getSimpleName());
            } else {
                model.put("baseVOName", CoreVO.class.getName());
                model.put("baseVOSimpleName", CoreVO.class.getSimpleName());
            }
            Try.run(
                    () -> {
                        try (HikariDataSource dataSource =
                                        GenerateUtil.getDataSource(
                                                this.jdbcUrl, this.username, this.password);
                                Connection connection = dataSource.getConnection();
                                PreparedStatement columnState =
                                        connection.prepareStatement(
                                                "SHOW FULL COLUMNS FROM " + this.table);
                                ResultSet columnSet = columnState.executeQuery();
                                PreparedStatement tableState =
                                        connection.prepareStatement(
                                                "SHOW CREATE TABLE " + this.table);
                                ResultSet tableSet = tableState.executeQuery()) {
                            List<Column> columns = new ArrayList<>();
                            while (columnSet.next()) {
                                Column column = new Column();
                                column.setColumnName(columnSet.getString("Field"));
                                column.setColumnType(columnSet.getString("Type"));
                                column.setColumnComment(columnSet.getString("Comment"));
                                column.setJavaName(
                                        CaseFormat.LOWER_UNDERSCORE.to(
                                                CaseFormat.LOWER_CAMEL, column.getColumnName()));
                                Class<?> javaType =
                                        GenerateUtil.getJavaType(column.getColumnType());
                                column.setImportClass(javaType.getName());
                                column.setJavaType(javaType.getSimpleName());
                                columns.add(column);
                            }
                            model.put("columns", columns);
                            while (tableSet.next()) {
                                model.putIfAbsent(
                                        "tableComment",
                                        GenerateUtil.findTableComment(
                                                tableSet.getString("Create Table")));
                            }
                        }
                    });
            System.out.println("build Generator completed");
            return generator;
        }
    }

    public Generator generate(Model model) {
        System.out.println("generate " + model.getSuffix() + " start");
        if (model == Model.ENTITY) {
            EntityModel.build(this.dataModel);
        } else if (model == Model.DTO) {
            DTOModel.build(this.dataModel);
        } else if (model == Model.CONVERTER) {
            ConverterModel.build(this.dataModel);
        } else if (model == Model.VO) {
            VOModel.build(this.dataModel);
        } else if (model == Model.QUERY) {
            QueryModel.build(this.dataModel);
        } else if (model == Model.MAPPER) {
            MapperModel.build(this.dataModel);
            GenerateUtil.generate(this.dataModel, model, FileType.XML, this.options);
        } else if (model == Model.SERVICE) {
            ServiceModel.build(this.dataModel);
            GenerateUtil.generate(this.dataModel, model, FileType.IMPL, this.options);
        } else if (model == Model.CONTROLLER) {
            ControllerModel.build(this.dataModel);
        }
        GenerateUtil.generate(this.dataModel, model, FileType.JAVA, this.options);
        System.out.println("generate " + model.getSuffix() + " completed");
        return this;
    }
}
